package monitor

import (
	"time"
)

// Cache for cpu and processes
type Cache struct {
	cpus      map[int]CPUInfo
	processes map[int]ProcessInfo
}

// Sample unit for objects
type Sample struct {
	sys         SysInfo
	withProcess []string
	withCPU     bool
	withMem     bool

	mem       MemInfo
	cpus      map[int]CPUInfo
	processes map[int]ProcessInfo

	cpuCache     map[int]CPUInfo
	processCache map[int]ProcessInfo

	cpuNum  int
	cpuType int
}

// NewSample returns sample object
func NewSample(sys SysInfo, cpu, mem bool, processes []string) Sample {
	sample := Sample{
		sys:         sys,
		withProcess: processes,
		withCPU:     cpu,
		withMem:     mem,
	}

	sample.cpuNum = sys.GetCPUNum()
	sample.cpuType = CPUSum

	sample.cpus = make(map[int]CPUInfo)
	sample.cpuCache = make(map[int]CPUInfo)

	sample.processes = make(map[int]ProcessInfo)
	sample.processCache = make(map[int]ProcessInfo)

	return sample
}

func (s *Sample) getPrevCPUInfo(i int) *CPUInfo {
	a, ok := s.cpuCache[i]
	if ok {
		return &a
	}
	return nil
}

func (s *Sample) getPrevProcessInfo(pid int) *ProcessInfo {
	a, ok := s.processCache[pid]
	if ok {
		return &a
	}
	return nil
}

// GetProcessPids returns pids of processes
func (s *Sample) GetProcessPids() []int {
	var pids []int

	for _, p := range s.processes {
		pids = append(pids, p.pid)
	}

	return pids
}

// Action does a sample action
func (s *Sample) Action() (time.Duration, error) {

	beginMark := time.Now()

	ppids := make(map[string][]int)

	if len(s.withProcess) > 0 {
		var err error
		ppids, err = sys.GetProcessPids(s.withProcess)
		if err != nil {
			return 0, err
		}
	}

	if s.withMem {
		err := s.sys.GetMemInfo(&s.mem)

		if err != nil {
			return 0, err
		}
	}

	if s.withCPU {

		/* backup old value */
		for i, p := range s.cpus {
			s.cpuCache[i] = p
		}

		/* make array */
		cpus := make([]CPUInfo, s.cpuNum)
		n, err := s.sys.GetCPUInfo(s.cpuType, cpus)
		if err != nil {
			return 0, err
		}

		for i := 0; i < n; i++ {
			s.cpus[i] = cpus[i]
		}
	}

	/* backup old values */
	for _, p := range s.processes {
		s.processCache[p.pid] = s.processes[p.pid]
	}

	/* get processes stat */
	for _, v := range ppids {

		for _, pid := range v {
			p := ProcessInfo{}
			err := sys.GetProcessInfo(pid, &p)
			if err == nil {
				s.processes[pid] = p
			}
		}
	}

	endMark := time.Now()
	return endMark.Sub(beginMark), nil
}

// GetCPULoad return the load average
func (s *Sample) GetCPULoad(id int) float32 {

	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		return (float32(c.busy-prev.busy) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUUser return the load average
func (s *Sample) GetCPUUser(id int) float32 {

	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		prev := s.getPrevCPUInfo(id)

		return (float32(c.usr-prev.usr) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUSys return the load average
func (s *Sample) GetCPUSys(id int) float32 {

	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		return (float32(c.sys-prev.sys) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUNice return the load average
func (s *Sample) GetCPUNice(id int) float32 {

	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		return (float32(c.nice-prev.nice) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUIdle return the load average
func (s *Sample) GetCPUIdle(id int) float32 {

	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		return (float32(c.idle-prev.idle) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUIoWait return the load average
func (s *Sample) GetCPUIoWait(id int) float32 {
	/*
		if s.withCPU == false {
			return 0
		}
	*/
	c, ok := s.cpus[id]
	prev := s.getPrevCPUInfo(id)

	if ok && (prev != nil) {
		return (float32(c.iowait-prev.iowait) / float32(c.total-prev.total)) * 100
	}

	return 0
}

// GetCPUTimeStamp return sample time stamp
func (s *Sample) GetCPUTimeStamp() time.Time {

	if s.withCPU {
		c, ok := s.cpus[0]
		if ok {
			return c.stamp
		}
	}

	return time.Now()
}

// GetMemUsaged return the mem usaged
func (s *Sample) GetMemUsaged() float32 {

	if s.withMem {
		return (float32(s.mem.total-s.mem.free) / float32(s.mem.total)) * 100
	}

	return 0
}

// GetMemTotalSize return the mem totalsize
func (s *Sample) GetMemTotalSize() uint64 {

	if s.withMem {
		return s.mem.total
	}

	return 0
}

// GetMemFreeSize return the mem free size
func (s *Sample) GetMemFreeSize() uint64 {

	if s.withMem {
		return s.mem.free
	}

	return 0
}

// GetMemCacheSize return the mem free size
func (s *Sample) GetMemCacheSize() uint64 {

	if s.withMem {
		return s.mem.cached
	}

	return 0
}

// GetMemBufferSize return the mem free size
func (s *Sample) GetMemBufferSize() uint64 {

	if s.withMem {
		return s.mem.buffers
	}

	return 0
}

// GetMemTimeStamp return sample time stamp
func (s *Sample) GetMemTimeStamp() time.Time {

	if s.withMem {
		return s.mem.stamp
	}

	return time.Now()
}

// GetProcessCPULoad return the process cpu load
func (s *Sample) GetProcessCPULoad(pid int) float32 {

	prev := s.getPrevProcessInfo(pid)

	p, ok := s.processes[pid]
	if ok && (prev != nil) {

		delta := p.utime - prev.utime
		delta += (p.stime - prev.stime)

		rate := (float32(delta) / float32(p.sysCPUTime-prev.sysCPUTime)) * 100

		//cpu is limited to cpu number
		//rate = rate * float32(p.sysCPUNum)

		//if rate > 100.0 {
		//	rate = 100.0
		//}

		return rate
	}

	return 0
}

// GetProcessMemUsaged return the process mem usaged
func (s *Sample) GetProcessMemUsaged(pid int) float32 {

	p, ok := s.processes[pid]
	if ok {
		return (float32(p.rss) / float32(p.sysMemSize)) * 100
	}

	return 0
}

// GetProcessVMemSize return the process virtual mem size
func (s *Sample) GetProcessVMemSize(pid int) uint64 {

	p, ok := s.processes[pid]
	if ok {
		return p.vsize
	}

	return 0
}

// GetProcessMemSize return the process  mem size
func (s *Sample) GetProcessMemSize(pid int) uint64 {

	p, ok := s.processes[pid]
	if ok {
		return p.rss
	}

	return 0
}

// GetProcessName return the process name
func (s *Sample) GetProcessName(pid int) string {

	a, ok := s.processes[pid]
	if ok {
		return a.name
	}

	return ""
}

// GetProcessTimeStamp return the process name
func (s *Sample) GetProcessTimeStamp(pid int) time.Time {

	p, ok := s.processes[pid]
	if ok {
		return p.stamp
	}

	return time.Now()
}
